package fr.sedoo.commons.metadata.utils.domain;

import java.lang.reflect.Field;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;

import org.apache.commons.lang.StringUtils;
import org.geotoolkit.gml.xml.v311.TimeIndeterminateValueType;
import org.geotoolkit.gml.xml.v311.TimePeriodType;
import org.geotoolkit.gml.xml.v311.TimePositionType;
import org.geotoolkit.internal.simple.SimpleReferenceIdentifier;
import org.geotoolkit.metadata.iso.DefaultMetadata;
import org.geotoolkit.metadata.iso.citation.DefaultAddress;
import org.geotoolkit.metadata.iso.citation.DefaultCitation;
import org.geotoolkit.metadata.iso.citation.DefaultCitationDate;
import org.geotoolkit.metadata.iso.citation.DefaultContact;
import org.geotoolkit.metadata.iso.citation.DefaultOnlineResource;
import org.geotoolkit.metadata.iso.citation.DefaultResponsibleParty;
import org.geotoolkit.metadata.iso.constraint.DefaultConstraints;
import org.geotoolkit.metadata.iso.constraint.DefaultLegalConstraints;
import org.geotoolkit.metadata.iso.distribution.DefaultDigitalTransferOptions;
import org.geotoolkit.metadata.iso.distribution.DefaultDistribution;
import org.geotoolkit.metadata.iso.extent.DefaultExtent;
import org.geotoolkit.metadata.iso.extent.DefaultGeographicBoundingBox;
import org.geotoolkit.metadata.iso.extent.DefaultTemporalExtent;
import org.geotoolkit.metadata.iso.identification.DefaultBrowseGraphic;
import org.geotoolkit.metadata.iso.identification.DefaultDataIdentification;
import org.geotoolkit.metadata.iso.identification.DefaultKeywords;
import org.geotoolkit.metadata.iso.lineage.DefaultLineage;
import org.geotoolkit.metadata.iso.maintenance.DefaultMaintenanceInformation;
import org.geotoolkit.metadata.iso.quality.DefaultDataQuality;
import org.geotoolkit.referencing.CRS;
import org.geotoolkit.temporal.object.DefaultPeriod;
import org.geotoolkit.util.DefaultInternationalString;
import org.geotoolkit.util.SimpleInternationalString;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.CitationDate;
import org.opengis.metadata.citation.DateType;
import org.opengis.metadata.citation.OnlineResource;
import org.opengis.metadata.citation.ResponsibleParty;
import org.opengis.metadata.citation.Role;
import org.opengis.metadata.constraint.LegalConstraints;
import org.opengis.metadata.distribution.DigitalTransferOptions;
import org.opengis.metadata.distribution.Format;
import org.opengis.metadata.extent.Extent;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.metadata.extent.GeographicExtent;
import org.opengis.metadata.identification.BrowseGraphic;
import org.opengis.metadata.identification.CharacterSet;
import org.opengis.metadata.identification.Keywords;
import org.opengis.metadata.identification.Progress;
import org.opengis.metadata.lineage.Lineage;
import org.opengis.metadata.maintenance.MaintenanceFrequency;
import org.opengis.metadata.maintenance.MaintenanceInformation;
import org.opengis.metadata.quality.DataQuality;
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.referencing.ReferenceSystem;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.temporal.TemporalPrimitive;
import org.opengis.util.InternationalString;

import fr.sedoo.commons.metadata.shared.DefaultResourceIdentifier;
import fr.sedoo.commons.metadata.shared.DefaultResourceLink;
import fr.sedoo.commons.metadata.shared.ResourceIdentifier;
import fr.sedoo.commons.metadata.shared.ResourceLink;
import fr.sedoo.commons.metadata.utils.xml.RBVNamespaces;

/**
 * Metadonnées proposant une façade simplifiée de la classe DefaultMetadata de
 * GeoToolkit<br/>
 * Les propriétés actuellement gérées sont:<br/>
 * <ul>
 * <li>IDENTIFICATION:
 * <li>resourceTitle</li>
 * <li>resourceAlternateTitle</li>
 * <li>resourceAbstract</li>
 * <li>resourceLanguages</li>
 * <li>resourceURL</li>
 * <li>status</li>
 * <li>maintenanceFrequency</li>
 * <li>resourceSnapshot</li>
 * <li>resourceDOI</li>
 * <li>keywords</li>
 * <li>useCondition</li>
 * <li>creationDate</li>
 * <li>publicationData</li>
 * <li>metadataDate</li>
 * <li>lastRevisionDate</li>
 * <li>geographicBoundingBox</li>
 * <li>uuid</li>
 * </ul>
 * 
 * @author F. Andre
 * 
 */

@XmlRootElement(name = "MD_Metadata", namespace = RBVNamespaces.GMD_NAMESPACE)
public class RBVMetadata extends DefaultMetadata
{
	private final String EMPTY_STRING = "";
	
	//Used for backward compatibility
	private HashMap<String, String> dustbin = new HashMap();
	public final static String OBSERVATORY="observatory";
	public final static String EXPERIMENTAL_SITE="experimentalSite";

	public RBVMetadata()
	{
		super();
//		setLocales(newValues)
	}

	public RBVMetadata(org.opengis.metadata.Metadata metadata)
	{
		super(metadata);
	}


	public void setResourceTitle(Map<Locale, String> values, String defaultValue)
	{
		DefaultCitation citation = MetadataTools.getCitation(this);
		citation.setTitle(getInternationalStringFromValues(values, defaultValue));
	}

	public Map<Locale, String> getResourceTitle(Locale metadataLanguage, List<Locale> alternateLanguages)
	{
		DefaultCitation citation = MetadataTools.getCitation(this);
		return getValuesFromInternationalString(metadataLanguage, alternateLanguages, citation.getTitle());
	}

	
	public void setResourceAlternateTitle(String resourceAlternateTitle)
	{
		DefaultCitation citation = MetadataTools.getCitation(this);
		citation.setAlternateTitles(Collections.singletonList(new SimpleInternationalString(StringUtils.trimToEmpty(resourceAlternateTitle))));
	}

	@XmlTransient
	public String getResourceAlternateTitle()
	{
		DefaultCitation citation = MetadataTools.getCitation(this);
		Collection<InternationalString> alternateTitles = citation.getAlternateTitles();
		String result = null;
		if ((alternateTitles != null) && (alternateTitles.isEmpty() == false))
		{
			result = alternateTitles.iterator().next().toString();
		}
		return StringUtils.defaultString(result);
	}

	public Map<Locale, String> getResourceAbstract(Locale metadataLanguage, List<Locale> languages)
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		return getValuesFromInternationalString(metadataLanguage, languages, dataIdentification.getAbstract());
	}
	
	private Map<Locale, String> getValuesFromInternationalString(Locale metadataLanguage, List<Locale> alternateLanguages, InternationalString value)
	{
		
		Map<Locale, String> result = new HashMap<Locale, String>();
		String defaultValue ="";
		if (value != null)
		{
			if (value instanceof DefaultInternationalString)
			{
				result =  getPrivateLocaleMap((DefaultInternationalString) value);
				defaultValue = StringUtils.trimToEmpty(value.toString());
			}
		}

		for (Locale locale : alternateLanguages) 
		{
			if (result.get(locale)==null)
			{
				result.put(locale,"");
			}
		}
		//We add the default value in case it doesn't match an alternateLanguage
		result.put(null, defaultValue);
		return result;
	}
	
	/*
	 * DefaultInternationString build the desired map correctly but doesn't expose it in a covenient way
	 * We get it throw introspection...
	 */
	private Map<Locale, String> getPrivateLocaleMap(DefaultInternationalString value) {
		try
		{
		Field privateField = DefaultInternationalString.class.getDeclaredField("localeMap");
		privateField.setAccessible(true);
		
		Map<Locale, String> aux = (Map<Locale, String>) privateField.get(value);
		if (aux == null)
		{
			return new HashMap<Locale, String>();
		}
		else
		{
			//We clone the map because geotoolkit sometimes return unmodifyable maps
			Map<Locale, String> result = new HashMap<Locale, String>();
			result.putAll(aux);
			return result;
		}
		}
		catch (Exception e)
		{
			//Can't be any
			return new HashMap<Locale, String>();
		}
	}

	private String getLocalizedString(InternationalString value, Locale locale)
	{
		if (value == null)
		{
			return EMPTY_STRING;
		}
		else
		{
			//Si on demande la locale par defaut - on fait un simple toString
			if (locale == null)
			{
				return StringUtils.trimToEmpty(value.toString());
			}
			else
			{
				//Si la locale demand� et la locale par d�faut, on fait aussi un simple toString
				if (Locale.getDefault().getLanguage().compareTo(locale.getLanguage())==0)
				{
					return StringUtils.trimToEmpty(value.toString());
				}
				
				
				//On regarde si une locale diff�rente de la default existe
				//Si c'est le cas on retourne cette valeur
				//Sinon c'est une chaine vide qui est retoun�e
				
				
				String localized = StringUtils.trimToEmpty(value.toString(locale));
				String defaultValue = StringUtils.trimToEmpty(value.toString());
				if (localized.compareTo(defaultValue)==0)
				{
					return EMPTY_STRING;
				}
				else
				{
					return localized;
				}
			}
		}
	}
	

	public void setResourceAbstract(Map<Locale, String> values, String defaultValue)
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		dataIdentification.setAbstract(getInternationalStringFromValues(values, defaultValue));
	}

	public void setResourceIdentifiers(List<? extends ResourceIdentifier> identifiers)
	{
		DefaultCitation citation = MetadataTools.getCitation(this);
		Iterator<? extends ResourceIdentifier> iterator = identifiers.iterator();
		List<Identifier> aux = new ArrayList<Identifier>();
		while (iterator.hasNext())
		{
			ResourceIdentifier resourceIdentifier = iterator.next();
			SimpleReferenceIdentifier identifier = new SimpleReferenceIdentifier(resourceIdentifier.getNameSpace(), resourceIdentifier.getCode());
			aux.add(identifier);
		}
		citation.setIdentifiers(aux);
	}

	@XmlTransient
	public List<ResourceIdentifier> getResourceIdentifiers()
	{
		List<ResourceIdentifier> resourceIdentifiers = new ArrayList<ResourceIdentifier>();
		Collection<Identifier> identifiers = MetadataTools.getCitation(this).getIdentifiers();
		if (identifiers != null)
		{
			Iterator<Identifier> iterator = identifiers.iterator();
			while (iterator.hasNext())
			{
				DefaultResourceIdentifier aux = new DefaultResourceIdentifier();
				Identifier identifier = iterator.next();
				aux.setCode(identifier.getCode());
				if (identifier instanceof ReferenceIdentifier)
				{
					aux.setNameSpace(((ReferenceIdentifier) identifier).getCodeSpace());
				}
				resourceIdentifiers.add(aux);
			}
		}
		return resourceIdentifiers;
	}


	/**
	 * Retourne la liste des codes correspondants aux langages du <b>jeu de
	 * donnÃ©es<b/>
	 * 
	 * @return
	 */
	@XmlTransient
	public List<String> getResourceLanguages()
	{
		List<String> codes = new ArrayList<String>();
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Collection<Locale> languages = dataIdentification.getLanguages();
		if (languages == null)
		{
			return codes;
		}
		Iterator<Locale> iterator = languages.iterator();
		while (iterator.hasNext())
		{
			Locale locale = iterator.next();
			// TODO gérer proprement le cas FRE/FRA
			if (locale.getISO3Language().compareToIgnoreCase("fra") == 0)
			{
				codes.add("fre");
			} else
			{
				codes.add(locale.getISO3Language());
			}
		}
		return codes;
	}

	/**
	 * Used bu unmarshalling
	 * 
	 * @param codes
	 */
	public void setResourceLanguagesFromWrappedString(List<WrappedString> codes)
	{
		setResourceLanguages(WrappedString.toStringList(codes));
	}

	/**
	 * Affecte la liste des codes correspondants aux langages du <b>jeu de
	 * données<b/>
	 * 
	 * @return
	 */
	public void setResourceLanguages(List<String> codes)
	{
		List<Locale> languages = new ArrayList<Locale>();
		Iterator<String> iterator = codes.iterator();
		while (iterator.hasNext())
		{
			String code = iterator.next();
			Locale locale = MetadataTools.getLocaleFromISO3(code);
			languages.add(locale);
		}
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		dataIdentification.setLanguages(languages);

	}

	/**
	 * Affecte le code correspondant au langage de la fiche de
	 * <b>métadonnées<b/>
	 * 
	 * @return
	 */

	public void setMetadataLanguage(String code)
	{
		Locale locale = MetadataTools.getLocaleFromISO3(code);
		setLanguage(locale);
	}

	/**
	 * Retourne le code correspondant au langage de la fiche de
	 * <b>métadonnées<b/>
	 * 
	 * @return
	 */
	@XmlTransient
	public String getMetadataLanguage()
	{
		if (getLanguage() == null)
		{
			return EMPTY_STRING;
		} else
		{
			// TODO gérer proprement le cas FRE/FRA
			if (getLanguage().getISO3Language().compareToIgnoreCase("fra") == 0)
			{
				return "fre";
			} else
			{
				return getLanguage().getISO3Language();
			}
		}
	}

	@XmlTransient
	public List<Contact> getResourceContacts()
	{
		List<Contact> result = new ArrayList<Contact>();
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Collection<ResponsibleParty> pointOfContacts = dataIdentification.getPointOfContacts();
		if (pointOfContacts != null)
		{
			Iterator<ResponsibleParty> iterator = pointOfContacts.iterator();
			while (iterator.hasNext())
			{
				result.add(contactFromResponsiblePary(iterator.next()));
			}
		}
		return result;
	}

	@XmlTransient
	public List<Contact> getMetadataContacts()
	{
		List<Contact> result = new ArrayList<Contact>();
		Collection<ResponsibleParty> pointOfContacts = getContacts();
		if (pointOfContacts != null)
		{
			Iterator<ResponsibleParty> iterator = pointOfContacts.iterator();
			while (iterator.hasNext())
			{
				result.add(contactFromResponsiblePary(iterator.next()));
			}
		}
		return result;
	}
	
	@XmlTransient
	public List<Contact> getOwnerContacts()
	{
		return getContactsByRole(Role.OWNER.name());
	}
	
	@XmlTransient
	public List<Contact> getDataContacts()
	{
		return getContactsByRole(Role.POINT_OF_CONTACT.name());
	}
	
	@XmlTransient
	public List<Contact> getPIContacts()
	{
		return getContactsByRole(Role.PRINCIPAL_INVESTIGATOR.name());
	}
	
	@XmlTransient
	public List<Contact> getOtherContacts()
	{
		List<Contact> result = new ArrayList<Contact>();
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Collection<ResponsibleParty> pointOfContacts = dataIdentification.getPointOfContacts();
		if (pointOfContacts != null)
		{
			Iterator<ResponsibleParty> iterator = pointOfContacts.iterator();
			while (iterator.hasNext())
			{
				ResponsibleParty current = iterator.next();
				if (current.getRole()== null)
				{
					continue;
				}
				
				if (isOtherRole(current.getRole().name()))
				{
					result.add(contactFromResponsiblePary(current));
				}
			}
		}
		return result;
	}
	
	private boolean isOtherRole(String role)
	{
		if (role.compareToIgnoreCase(Role.PRINCIPAL_INVESTIGATOR.name())==0)
		{
			return false;
		}
		if (role.compareToIgnoreCase(Role.OWNER.name())==0)
		{
			return false;
		}
		if (role.compareToIgnoreCase(Role.POINT_OF_CONTACT.name())==0)
		{
			return false;
		}
		return true;
	}
	
	
	private List<Contact> getContactsByRole(String role)
	{
		List<Contact> result = new ArrayList<Contact>();
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Collection<ResponsibleParty> pointOfContacts = dataIdentification.getPointOfContacts();
		if (pointOfContacts != null)
		{
			Iterator<ResponsibleParty> iterator = pointOfContacts.iterator();
			while (iterator.hasNext())
			{
				ResponsibleParty current = iterator.next();
				if (current.getRole().name().compareToIgnoreCase(role)==0)
				{
					result.add(contactFromResponsiblePary(current));
				}
			}
		}
		return result;
	}

	private Contact contactFromResponsiblePary(ResponsibleParty responsibleParty)
	{
		Contact contact = new Contact();
		contact.setIndividualName(responsibleParty.getIndividualName());
		contact.setOrganisationName(responsibleParty.getOrganisationName().toString());
		if (responsibleParty.getRole() != null)
		{
			if (StringUtils.isNotEmpty(responsibleParty.getRole().identifier()))
			{
				contact.setRole(responsibleParty.getRole().identifier());
			}
			else
			{
				//Il arrive des cas - juste après la création - l'identifier est null. Dans ce cas le name a la bonne valeur...
				contact.setRole(responsibleParty.getRole().name());
			}
		}

		if (responsibleParty.getContactInfo().getAddress() != null)
		{
			try
			{
				contact.setEmailAddress(responsibleParty.getContactInfo().getAddress().getElectronicMailAddresses().iterator().next());
			} catch (Exception e)
			{

			} 

			if (responsibleParty.getContactInfo().getAddress().getPostalCode() != null)
			{
				contact.setZipCode(StringUtils.trimToEmpty(responsibleParty.getContactInfo().getAddress().getPostalCode()));
			}
			if (responsibleParty.getContactInfo().getAddress().getCity() != null)
			{
				contact.setCity(StringUtils.trimToEmpty(responsibleParty.getContactInfo().getAddress().getCity().toString()));
			}
			if (responsibleParty.getContactInfo().getAddress().getCountry() != null)
			{
				contact.setCountry(StringUtils.trimToEmpty(responsibleParty.getContactInfo().getAddress().getCountry().toString()));
			}
			if ((responsibleParty.getContactInfo().getAddress().getDeliveryPoints() != null) && (responsibleParty.getContactInfo().getAddress().getDeliveryPoints().isEmpty() == false))
			{
				contact.setAddress(StringUtils.trimToEmpty(responsibleParty.getContactInfo().getAddress().getDeliveryPoints().iterator().next().toString()));
			}
		}
		
		return contact;
	}

	public void setResourceContacts(List<Contact> contacts)
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Collection<ResponsibleParty> aux = new ArrayList<ResponsibleParty>();
		if (contacts != null)
		{
			Iterator<Contact> iterator = contacts.iterator();
			while (iterator.hasNext())
			{
				aux.add(contactToResponsibleParty(iterator.next()));
			}
		}
		dataIdentification.setPointOfContacts(aux);
	}

	public void setMetadataContacts(List<Contact> contacts)
	{
		Collection<ResponsibleParty> pointOfContacts = new ArrayList<ResponsibleParty>();
		if (contacts != null)
		{
			Iterator<Contact> iterator = contacts.iterator();
			while (iterator.hasNext())
			{
				pointOfContacts.add(contactToResponsibleParty(iterator.next()));
			}
		}
		setContacts(pointOfContacts);
	}

	private ResponsibleParty contactToResponsibleParty(Contact contact)
	{
		DefaultResponsibleParty current = new DefaultResponsibleParty();
		current.setIndividualName(contact.getIndividualName());
		current.setRole(Role.valueOf(contact.getRole()));
		current.setOrganisationName(new DefaultInternationalString(contact.getOrganisationName()));
		DefaultContact aux = new DefaultContact();
		DefaultAddress address = new DefaultAddress();
		address.setElectronicMailAddresses(Collections.singletonList(contact.getEmailAddress()));
		aux.setAddress(address);
		current.setContactInfo(aux);
		return current;
	}

	@XmlTransient
	public List<String> getKeywords()
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Collection<Keywords> descriptiveKeywords = dataIdentification.getDescriptiveKeywords();
		List<String> keywords = new ArrayList<String>();
		if (descriptiveKeywords != null)
		{
			Iterator<Keywords> iterator = descriptiveKeywords.iterator();
			while (iterator.hasNext())
			{
				Keywords aux = iterator.next();
				String trimed = StringUtils.defaultString(aux.toString());
				if (trimed.length() != 0)
				{
					keywords.add(aux.toString());
				}
			}
		}
		return keywords;
	}

	public void setKeywords(List<String> keywords)
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Collection<Keywords> values = new ArrayList<Keywords>();

		Iterator<String> iterator = keywords.iterator();
		while (iterator.hasNext())
		{
			String keyword = iterator.next();
			DefaultKeywords aux = new DefaultKeywords();
			DefaultInternationalString internationalString = new DefaultInternationalString(keyword);
			aux.setKeywords(Collections.singletonList(internationalString));
			values.add(aux);
		}

		dataIdentification.setDescriptiveKeywords(values);
	}

	

	public void setResourceURL(List<? extends ResourceLink> urls)
	{
		DefaultDistribution distributionInfo = (DefaultDistribution) getDistributionInfo();
		if (distributionInfo == null)
		{
			distributionInfo = new DefaultDistribution();
		}

		Collection<DigitalTransferOptions> transferOptions = distributionInfo.getTransferOptions();

		if (transferOptions == null)
		{
			transferOptions = new ArrayList<DigitalTransferOptions>();
			distributionInfo.setTransferOptions(transferOptions);
		}

		if (urls != null)
		{
			Iterator<? extends ResourceLink> iterator = urls.iterator();
			while (iterator.hasNext())
			{
				ResourceLink url = iterator.next();
				DefaultDigitalTransferOptions currentTransferOptions = new DefaultDigitalTransferOptions();
				DefaultOnlineResource onlineResource = new DefaultOnlineResource();
				try
				{
					onlineResource.setLinkage(new URI(url.getLink()));
					onlineResource.setName(url.getLabel());
					if (StringUtils.isEmpty(url.getProtocol()))
					{
						//We set HTTP protocol by default
						onlineResource.setProtocol(ResourceLink.LINK_PROTOCOL);
					}
					else
					{
						onlineResource.setProtocol(url.getProtocol());
					}
				} catch (URISyntaxException e)
				{
					// Ignored exception
				}
				currentTransferOptions.setOnLines(Collections.singletonList(onlineResource));
				transferOptions.add(currentTransferOptions);
			}
		}

		setDistributionInfo(distributionInfo);
	}

	@XmlTransient
	public List<ResourceLink> getResourceURL()
	{
		List<ResourceLink> urls = new ArrayList<ResourceLink>();

		DefaultDistribution distributionInfo = (DefaultDistribution) getDistributionInfo();
		if (distributionInfo != null)
		{
			Collection<DigitalTransferOptions> transferOptions = distributionInfo.getTransferOptions();
			if (transferOptions != null)
			{
				Iterator<DigitalTransferOptions> iterator = transferOptions.iterator();
				while (iterator.hasNext())
				{
					DigitalTransferOptions currentTransferOptions = iterator.next();
					Collection<? extends OnlineResource> onLines = currentTransferOptions.getOnLines();
					Iterator<? extends OnlineResource> onLinesterator = onLines.iterator();
					while (onLinesterator.hasNext())
					{
						OnlineResource onlineResource = onLinesterator.next();
						DefaultResourceLink url = new DefaultResourceLink();
						url.setProtocol(onlineResource.getProtocol());
						url.setLabel(onlineResource.getName());
						url.setLink(onlineResource.getLinkage().toString());
						urls.add(url);
					}
				}
			}
		}

		return urls;
	}

	@XmlTransient
	public List<DescribedURL> getSnapshotURL()
	{

		List<DescribedURL> urls = new ArrayList<DescribedURL>();

		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Collection<BrowseGraphic> graphicOverviews = dataIdentification.getGraphicOverviews();

		if (graphicOverviews != null)
		{
			Iterator<BrowseGraphic> iterator = graphicOverviews.iterator();
			while (iterator.hasNext())
			{
				BrowseGraphic browseGraphic = iterator.next();
				String label = "";
				if (browseGraphic.getFileDescription() != null)
				{
					label = browseGraphic.getFileDescription().toString().trim();
				}
				String link = "";
				if (browseGraphic.getFileName() != null)
				{
					link = browseGraphic.getFileName().toString().trim();
				}

				if (link.length() > 0)
				{
					urls.add(new DescribedURL(link, label));
				}
			}
		}

		return urls;
	}

	public void setSnapshotURL(List<DescribedURL> urls)
	{

		Collection<BrowseGraphic> graphicOverviews = new ArrayList<BrowseGraphic>();
		Iterator<DescribedURL> iterator = urls.iterator();
		while (iterator.hasNext())
		{
			DescribedURL describedURL = iterator.next();
			String label = "";
			if (describedURL.getLabel() != null)
			{
				label = describedURL.getLabel().trim();
			}

			String link = "";
			if (describedURL.getLink() != null)
			{
				link = describedURL.getLink().trim();
			}

			if (link.length() > 0)
			{
				DefaultBrowseGraphic aux = new DefaultBrowseGraphic();
				try
				{
					aux.setFileName(new URI(link));
				} catch (URISyntaxException e)
				{
					// Non correct URI are ignored
					continue;
				}
				aux.setFileDescription(new DefaultInternationalString(label));
				graphicOverviews.add(aux);
			}
		}

		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		dataIdentification.setGraphicOverviews(graphicOverviews);

	}

	/**
	 * On considère qu'une telle date est unique Toute instance existante de
	 * type CI_DateTypeCode codeListValue="PUBLICATION" sera donc supprimée
	 * 
	 * @param publicationDate
	 */
	public void setPublicationDate(String publicationDateString)
	{
		setTypedDate(publicationDateString, DateType.PUBLICATION);
	}

	public void setCreationDate(String creationDateString)
	{
		setTypedDate(creationDateString, DateType.CREATION);
	}

	public void setLastRevisionDate(String revisionDateString)
	{
		setTypedDate(revisionDateString, DateType.REVISION);
	}

	/**
	 * On considÃ¨re qu'une telle date est unique (restriction par rapport Ã  la
	 * norme Inspire La premiÃ¨re instance existante de type CI_DateTypeCode
	 * codeListValue="publication" sera donc renvoyÃ©e
	 * 
	 * @param publicationDate
	 */
	@XmlTransient
	public String getPublicationDate()
	{
		return getTypedDate(DateType.PUBLICATION);
	}

	public void setTemporalExtent(String startDate, String endDate)
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Collection<Extent> extents = dataIdentification.getExtents();
		if (extents == null)
		{
			extents = new ArrayList<Extent>();
			dataIdentification.setExtents(extents);
		}
		DefaultExtent uniqueExtent = null;
		if (extents.isEmpty())
		{
			uniqueExtent = new DefaultExtent();
			extents.add(uniqueExtent);
		} else
		{
			uniqueExtent = new DefaultExtent(extents.iterator().next());
		}

		// TimePositionType position = new
		// TimePositionType(Calendar.getInstance().getTime());
		// TimePeriodType period = new TimePeriodType(position);

		DefaultTemporalExtent uniqueTemporalExtent = new DefaultTemporalExtent(Calendar.getInstance().getTime(), Calendar.getInstance().getTime());
		// TimePositionType begin = new TimePositionType();
		// begin.setFrame(startDate);
		// TimePeriodType period = new TimePeriodType();
		// period.setBeginPosition(begin);
		// period.setEndPosition(TimeIndeterminateValueType.NOW);

		// TimePeriod

		DefaultTemporalExtent defExtent = new DefaultTemporalExtent();
		List<DefaultTemporalExtent> defExtList = new ArrayList<DefaultTemporalExtent>();

		DefaultPeriod timePeriod = new DefaultPeriod();
		timePeriod.setBegining(Calendar.getInstance().getTime());
		timePeriod.setEnding(Calendar.getInstance().getTime());

		defExtent.setExtent(timePeriod); // set TemporalExtent
		defExtList.add(defExtent);

		uniqueExtent.setTemporalElements(defExtList);
		dataIdentification.setExtents(Collections.singletonList(uniqueExtent));

	}

	@XmlTransient
	public String getCreationDate()
	{
		return getTypedDate(DateType.CREATION);
	}

	@XmlTransient
	public String getLastRevisionDate()
	{
		return getTypedDate(DateType.REVISION);
	}

	private String getTypedDate(DateType type)
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		DefaultCitation citation = new DefaultCitation(dataIdentification.getCitation());

		Collection<CitationDate> dates = citation.getDates();
		Iterator<CitationDate> iterator = dates.iterator();
		while (iterator.hasNext())
		{
			CitationDate citationDate = iterator.next();
			if (citationDate.getDateType().compareTo(type) == 0)
			{
				return MetadataTools.formatDate(citationDate.getDate());
			}
		}

		return EMPTY_STRING;
	}

	private void setTypedDate(String dateString, DateType type)
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		DefaultCitation citation = (DefaultCitation) dataIdentification.getCitation();

		Collection<CitationDate> oldDates = citation.getDates();
		Collection<CitationDate> newDates = new ArrayList<CitationDate>();

		// On rajoute les dates n'Ã©tant pas de type indiqué
		Iterator<CitationDate> iterator = oldDates.iterator();
		while (iterator.hasNext())
		{
			CitationDate citationDate = iterator.next();
			if (citationDate.getDateType().compareTo(type) != 0)
			{
				newDates.add(citationDate);
			}
		}

		DefaultCitationDate publicationDate = new DefaultCitationDate();
		try
		{
			publicationDate.setDate(MetadataTools.parseStringToDate(dateString));
		} catch (Exception e)
		{
			publicationDate.setDate(null);
		}
		publicationDate.setDateType(type);
		newDates.add(publicationDate);
		citation.setDates(newDates);

	}

	@XmlTransient
	public String getMetadataDate()
	{
		if (getDateStamp() == null)
		{
			return "";
		} else
		{
			if (getDateStamp().getTime() == Long.MIN_VALUE)
			{
				return "";
			} else
			{
				return MetadataTools.formatDate(getDateStamp());
			}
		}
	}

	public void setMetadataDate(String dateString)
	{
		try
		{
			setDateStamp(MetadataTools.parseStringToDate(dateString));
		} catch (Exception e)
		{
			setDateStamp(null);
		}
	}

	public void setGeographicBoundingBox(DefaultGeographicBoundingBox box)
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Collection<Extent> extents = dataIdentification.getExtents();

		if (extents.isEmpty())
		{
			DefaultExtent uniqueExtent = new DefaultExtent();
			uniqueExtent.setGeographicElements(Collections.singletonList(box));
			dataIdentification.setExtents(Collections.singletonList(uniqueExtent));
		}

		else
		{
			Extent uniqueExtent = extents.iterator().next();
			if (uniqueExtent instanceof DefaultExtent)
			{
				((DefaultExtent) uniqueExtent).setGeographicElements(Collections.singletonList(box));
			} else
			{
				DefaultExtent aux = new DefaultExtent(uniqueExtent);
				aux.setGeographicElements(Collections.singletonList(box));
				dataIdentification.setExtents(Collections.singletonList(aux));
			}
		}
	}
	
	public void setGeographicBoundingBoxes(ArrayList<DefaultGeographicBoundingBox> boxes) 
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Collection<Extent> extents = dataIdentification.getExtents();

		if (extents.isEmpty())
		{
			DefaultExtent uniqueExtent = new DefaultExtent();
			uniqueExtent.setGeographicElements(boxes);
			dataIdentification.setExtents(Collections.singletonList(uniqueExtent));
		}

		else
		{
			Extent uniqueExtent = extents.iterator().next();
			if (uniqueExtent instanceof DefaultExtent)
			{
				((DefaultExtent) uniqueExtent).setGeographicElements(boxes);
			} else
			{
				DefaultExtent aux = new DefaultExtent(uniqueExtent);
				aux.setGeographicElements(boxes);
				dataIdentification.setExtents(Collections.singletonList(aux));
			}
		}
	}

	@XmlTransient
	public List<GeographicBoundingBox> getGeographicBoundingBoxes()
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Collection<Extent> extents = dataIdentification.getExtents();

		if (extents.isEmpty())
		{
			return new ArrayList<GeographicBoundingBox>();
		} else
		{
			Extent uniqueExtent = extents.iterator().next();
			Collection<? extends GeographicExtent> geographicElements = uniqueExtent.getGeographicElements();
			if (geographicElements.isEmpty())
			{
				return new ArrayList<GeographicBoundingBox>();
			} else
			{
				ArrayList<GeographicBoundingBox> result = new ArrayList<GeographicBoundingBox>(); 
				Iterator<? extends GeographicExtent> iterator = geographicElements.iterator();
				
				while (iterator.hasNext()) {
					GeographicExtent current = (GeographicExtent) iterator.next();
					if (current instanceof GeographicBoundingBox)
					{
						result.add((GeographicBoundingBox) current);
					}
				}
				
				return result;
				
			}
		}
	}
	

	@XmlTransient
	public String getUuid()
	{
		return getFileIdentifier();
	}

	public void setUuid(String value)
	{
		setFileIdentifier(value);
	}

	@XmlTransient
	public String getStatus()
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Collection<Progress> aux = dataIdentification.getStatus();
		if ((aux != null) && (aux.isEmpty() == false))
		{
			Progress status = aux.iterator().next();
			if (status != null)
			{
				return status.name();
			}
			else
			{
				return "";
			}
		}

		return null;
	}

	public void setStatus(String status)
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Progress aux = Progress.valueOf(status);
		dataIdentification.setStatus(Collections.singletonList(aux));
	}

	public void setResourceUpdateRythm(String rythm)
	{

		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		DefaultMaintenanceInformation mi = new DefaultMaintenanceInformation();
		mi.setMaintenanceAndUpdateFrequency(MaintenanceFrequency.valueOf(rythm));
		dataIdentification.setResourceMaintenances(Collections.singletonList(mi));
	}

	@XmlTransient
	public String getResourceUpdateRythm()
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Collection<MaintenanceInformation> aux = dataIdentification.getResourceMaintenances();
		if ((aux != null) && (aux.isEmpty() == false))
		{
			MaintenanceInformation mi = aux.iterator().next();
			MaintenanceFrequency maintenanceAndUpdateFrequency = mi.getMaintenanceAndUpdateFrequency();
			if (maintenanceAndUpdateFrequency != null)
			{
				return maintenanceAndUpdateFrequency.name();
			} else
			{
				return null;
			}
		}
		return null;
	}

	@XmlTransient
	public String getResourceBeginDate()
	{
		DefaultTemporalExtent temporalExtent = MetadataTools.getDefaultTemporalExtent(this);
		TemporalPrimitive temporalPrimitive = temporalExtent.getExtent();

		if (temporalPrimitive instanceof TimePeriodType)
		{
			return timePositionToString(((TimePeriodType) temporalPrimitive).getBeginPosition());
		} else
		{
			return timePositionToString(null);
		}
	}

	@XmlTransient
	public String getResourceEndDate()
	{
		DefaultTemporalExtent temporalExtent = MetadataTools.getDefaultTemporalExtent(this);
		TemporalPrimitive temporalPrimitive = temporalExtent.getExtent();

		if (temporalPrimitive instanceof TimePeriodType)
		{
			return timePositionToString(((TimePeriodType) temporalPrimitive).getEndPosition());
		} else
		{
			return timePositionToString(null);
		}
	}

	public void setResourceBeginDate(String beginDate)
	{
		TimePositionType beginPosition = timePositionFromString(beginDate);
		DefaultTemporalExtent temporalExtent = MetadataTools.getDefaultTemporalExtent(this);
		TemporalPrimitive temporalPrimitive = temporalExtent.getExtent();
		if (temporalPrimitive instanceof TimePeriodType)
		{
			TimePeriodType period = (TimePeriodType) temporalPrimitive;
			period.setBeginPosition(beginPosition);
		} else
		{
			// On ecrase l'existant avec une periode dont la date de fin est
			// inconnue;
			TimePeriodType period = new TimePeriodType(beginPosition);
			period.setEndPosition(timePositionFromString(""));
			temporalExtent.setExtent(period);
		}
	}

	public void setResourceEndDate(String endDate)
	{
		TimePositionType endPosition = timePositionFromString(endDate);
		DefaultTemporalExtent temporalExtent = MetadataTools.getDefaultTemporalExtent(this);
		TemporalPrimitive temporalPrimitive = temporalExtent.getExtent();
		if (temporalPrimitive instanceof TimePeriodType)
		{
			TimePeriodType period = (TimePeriodType) temporalPrimitive;
			period.setEndPosition(endPosition);
		} else
		{
			TimePeriodType period = new TimePeriodType(timePositionFromString(""));
			period.setEndPosition(endPosition);
			temporalExtent.setExtent(period);
		}
	}

	protected TimePositionType timePositionFromString(String value)
	{
		if ((value == null) || (value.length() == 0))
		{
			return new TimePositionType(TimeIndeterminateValueType.UNKNOWN);
		} else if (value.compareToIgnoreCase(TimeIndeterminateValueType.NOW.name()) == 0)
		{
			return new TimePositionType(TimeIndeterminateValueType.NOW);
		} else
		{
			try
			{
				Date date = MetadataTools.parseStringToDate(value);
				return new TimePositionType(date);
			} catch (Exception e)
			{
				return new TimePositionType(TimeIndeterminateValueType.UNKNOWN);
			}
		}

	}

	protected String timePositionToString(TimePositionType type)
	{
		if (type == null)
		{
			return EMPTY_STRING;
		} else
		{
			if (type.getIndeterminatePosition() == null)
			{
				return MetadataTools.formatDate(type.getDate());
			} else
			{
				if (type.getIndeterminatePosition().name().compareToIgnoreCase(TimeIndeterminateValueType.NOW.name()) == 0)
				{
					return TimeIndeterminateValueType.NOW.name().toLowerCase();
				}
			}
		}
		return EMPTY_STRING;
	}

	public void setUseConditions(Map<Locale, String> values, String defaultValue)
	{
		DefaultConstraints useConditionConstraint = MetadataTools.getUseConditionConstraint(this);
		useConditionConstraint.setUseLimitations(Collections.singletonList(getInternationalStringFromValues(values, defaultValue)));
	}

	
	public Map<Locale, String> getUseConditions(Locale metadataLanguage, List<Locale> alternateLanguages)
	{
		DefaultConstraints useConditionConstraint = MetadataTools.getUseConditionConstraint(this);
		
		if ((useConditionConstraint.getUseLimitations() == null) || (useConditionConstraint.getUseLimitations().isEmpty()))
		{
			return ensureAllLanguagesPresents(alternateLanguages, new HashMap<Locale, String>());
		} else
		{
			return getValuesFromInternationalString(metadataLanguage, alternateLanguages, useConditionConstraint.getUseLimitations().iterator().next());
		}
	}

	
	public void setPublicAccessLimitations(Map<Locale, String> values, String defaultValue)
	{
		DefaultLegalConstraints legalConstraints = MetadataTools.getPublicAccessLimitationConstraint(this);
		legalConstraints.setOtherConstraints(Collections.singletonList(getInternationalStringFromValues(values, defaultValue)));
	}
	
	

	public Map<Locale, String> getPublicAccessLimitations(Locale metadataLanguage, List<Locale> languages)
	{

		LegalConstraints legalConstraints = MetadataTools.getPublicAccessLimitationConstraint(this);
		Collection<? extends InternationalString> otherConstraints = legalConstraints.getOtherConstraints();
		Iterator<? extends InternationalString> iterator = otherConstraints.iterator();
		if (iterator.hasNext())
		{
			return getValuesFromInternationalString(metadataLanguage, languages,iterator.next());	
		}
		else
		{
			return ensureAllLanguagesPresents(languages, new HashMap<Locale, String>());
		}

	}

	private HashMap<Locale, String> ensureAllLanguagesPresents(List<Locale> languages, HashMap<Locale, String> values) 
			{
		for (Locale locale : languages) 
		{
			if (values.get(locale)==null)
			{
				values.put(locale,"");
			}
		}
		values.put(null,"");
		return values;
	}

	@XmlTransient
	public String getResourceEncodingCharset()
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Collection<CharacterSet> characterSets = dataIdentification.getCharacterSets();
		if ((characterSets != null) && (characterSets.isEmpty() == false))
		{
			CharacterSet next = characterSets.iterator().next();
			if (next != null)
			{
				return next.name();
			} else
			{
				return EMPTY_STRING;
			}
		} else
		{
			return EMPTY_STRING;
		}
	}

	public void setResourceEncodingCharset(String code)
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		CharacterSet characterSet = CharacterSet.valueOf(code);
		dataIdentification.setCharacterSets(Collections.singletonList(characterSet));
	}

	@XmlTransient
	public Format getResourceFormat()
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		Collection<Format> resourceFormats = dataIdentification.getResourceFormats();
		if ((resourceFormats != null) && (resourceFormats.isEmpty() == false))
		{
			return resourceFormats.iterator().next();
		} else
		{
			return null;
		}
	}

	public void setResourceFormat(Format format)
	{
		DefaultDataIdentification dataIdentification = MetadataTools.getFisrtIdentificationInfo(this);
		dataIdentification.setResourceFormats(Collections.singletonList(format));
	}

	@XmlTransient
	public String getCoordinateSystem()
	{
		Collection<ReferenceSystem> referenceSystemInfo = getReferenceSystemInfo();
		if (referenceSystemInfo == null)
		{
			return EMPTY_STRING;
		} else
		{
			Iterator<ReferenceSystem> iterator = referenceSystemInfo.iterator();
			while (iterator.hasNext())
			{
				ReferenceSystem referenceSystem = iterator.next();
				if (referenceSystem instanceof CoordinateReferenceSystem)
				{
					ReferenceIdentifier aux = ((CoordinateReferenceSystem) referenceSystem).getCoordinateSystem().getIdentifiers().iterator().next();
					return aux.getCodeSpace() + ":" + aux.getCode();
				}
			}
			return EMPTY_STRING;
		}
	}

	public void setCoordinateSystem(String code) throws Exception
	{
		CoordinateReferenceSystem crs = null;
		try
		{
			crs = CRS.decode(code);
		} catch (Exception e)
		{
			throw new Exception("Impossible de décrypter le code de CRS (" + code + ")", e);
		}
		Collection<ReferenceSystem> referenceSystemInfo = getReferenceSystemInfo();
		if (referenceSystemInfo == null)
		{
			referenceSystemInfo = new ArrayList<ReferenceSystem>();
		}
		// Suppression de tous les systèmes de coordonnées déjà présents
		Iterator<ReferenceSystem> iterator = referenceSystemInfo.iterator();
		List<ReferenceSystem> newValues = new ArrayList<ReferenceSystem>();

		while (iterator.hasNext())
		{
			ReferenceSystem referenceSystem = iterator.next();
			if (!(iterator instanceof CoordinateReferenceSystem))
			{
				newValues.add(referenceSystem);
			}
		}

		// Ajout du nouveau CRS
		newValues.add(crs);

		setReferenceSystemInfo(newValues);

	}

	public InternationalString getInternationalStringFromValues(Map<Locale, String> values, String defaultValue)
	{
		if ((values == null) || (values.isEmpty()))
		{
			return new SimpleInternationalString(EMPTY_STRING);
		}
		else
		{
			DefaultInternationalString aux = new DefaultInternationalString(defaultValue);
			Iterator<Locale> iterator = values.keySet().iterator();
			while (iterator.hasNext()) 
			{
				Locale language = (Locale) iterator.next();
				aux.add(language, values.get(language));
			}
			return aux;
		}
	}

	public HashMap<String, String> getDustbin() {
		return dustbin;
	}

	public void setDustbin(HashMap<String, String> dustbin) {
		this.dustbin = dustbin;
	}

	public void setResourceGenealogy(HashMap<Locale, String> values, String defaultValue) 
	{
		DefaultDataQuality dataQuality;
		if ((getDataQualityInfo() == null) || getDataQualityInfo().isEmpty())
		{
			dataQuality = new DefaultDataQuality();
		}
		else
		{
			dataQuality = new  DefaultDataQuality(getDataQualityInfo().iterator().next());
		}
		
		ArrayList<DataQuality> aux = new ArrayList<DataQuality>();
		aux.add(dataQuality);
		setDataQualityInfo(aux);
		
		DefaultLineage lineage;
		if (dataQuality.getLineage() == null)
		{
			lineage = new DefaultLineage();
		}
		else
		{
			lineage = new DefaultLineage(dataQuality.getLineage());
		}
		dataQuality.setLineage(lineage);
		lineage.setStatement(getInternationalStringFromValues(values, defaultValue));
	}

	
	public Map<Locale, String> getResourceGenealogy(Locale metadataLanguage, List<Locale> alternateLanguages)
	{
		if ((getDataQualityInfo() == null) || getDataQualityInfo().isEmpty())
		{
			return getValuesFromInternationalString(metadataLanguage, alternateLanguages, new DefaultInternationalString());
		}
		DataQuality dataQuality = getDataQualityInfo().iterator().next();
		if (dataQuality == null)
		{
			return getValuesFromInternationalString(metadataLanguage, alternateLanguages, new DefaultInternationalString());
		}
		Lineage lineage = dataQuality.getLineage();
		if (lineage == null)
		{
			return getValuesFromInternationalString(metadataLanguage, alternateLanguages, new DefaultInternationalString());
		}	
		InternationalString statement = lineage.getStatement();
		if (statement == null)
		{
			return getValuesFromInternationalString(metadataLanguage, alternateLanguages, new DefaultInternationalString());
		}
		return getValuesFromInternationalString(metadataLanguage, alternateLanguages, statement);
	}
	
}
